1 module hip.api.data.commons; 2 3 4 ///Use @Asset instead of HipAsset. 5 struct HipAssetUDA(T) 6 { 7 string path; 8 static if(!(is(T == void))) 9 T function(string data) conversionFunction; 10 int start, end; 11 } 12 13 /** 14 * Params: 15 * path = Path where the asset is located. 16 It may receive an $ for path formatting with numbers(only valid when array is used.) 17 * conversionFunc = A function with input the data located at "path", and return any data. 18 * start = For Arrays. Inclusive. May be greater than end for reverse counting. 19 * end = For Arrays. Inclusive. 20 * Returns: 21 */ 22 HipAssetUDA!T Asset(T)(string path, T function(string) conversionFunc, int start = 0, int end = 0){return HipAssetUDA!T(path, conversionFunc, start, end);} 23 /** 24 * Params: 25 * path = Path where the asset is located. 26 It may receive an $ for path formatting with numbers(only valid when array is used.) 27 * start = For Arrays. Inclusive. May be greater than end for reverse counting. 28 * end = For Arrays. Inclusive. 29 * Returns: 30 */ 31 HipAssetUDA!void Asset(string path, int start = 0, int end = 0){return HipAssetUDA!void(path, start, end);} 32 33 template FilterAsset(Attributes...) 34 { 35 import std.traits:isInstanceOf; 36 import std.meta:AliasSeq; 37 static foreach(attr; Attributes) 38 static if(isInstanceOf!(HipAssetUDA, typeof(attr))) 39 alias FilterAsset = attr; 40 } 41 42 template GetAssetUDA(Attributes...) 43 { 44 alias asset = FilterAsset!(Attributes); 45 static if(!is(typeof(asset) == void)) //Means it is a real struct. 46 enum GetAssetUDA = asset; 47 else 48 enum GetAssetUDA = HipAssetUDA!void(); 49 } 50 51 52 public string[] splitLines(string input) 53 { 54 string[] ret; 55 size_t lastCut = 0; 56 foreach(i, ch; input) 57 { 58 if(ch == '\n') 59 { 60 ret~= input[lastCut..i]; 61 lastCut = i+1; 62 } 63 } 64 if(lastCut < input.length) ret~= input[lastCut..$]; 65 return ret; 66 } 67 68 69 string[] getModulesFromRoot(string modules, string root) 70 { 71 string[] ret = splitLines(modules); 72 73 ptrdiff_t rootStart = -1; 74 foreach(i, mod; ret) 75 { 76 if(mod.length < root.length) 77 { 78 if(rootStart == -1) 79 continue; 80 else 81 return ret[rootStart..i]; 82 83 } 84 if(mod[0..root.length] == root) 85 { 86 if(rootStart == -1) 87 rootStart = i; 88 } 89 else if(rootStart != -1) 90 return ret[rootStart..i]; 91 } 92 assert(rootStart != -1, "Unable to find root "~root~" in modules list."); 93 return ret[rootStart..$]; 94 } 95 96 IHipAssetLoadTask loadAsset(type)(string assetPath) 97 { 98 import hip.api; 99 static if(is(type == IHipCSV)) 100 return HipAssetManager.loadCSV(assetPath); 101 else static if(is(type == IHipFont)) 102 return HipAssetManager.loadFont(assetPath); 103 else static if(is(type == IImage)) 104 return HipAssetManager.loadImage(assetPath); 105 else static if(is(type == IHipIniFile)) 106 return HipAssetManager.loadINI(assetPath); 107 else static if(is(type == IHipJSONC)) 108 return HipAssetManager.loadJSONC(assetPath); 109 else static if(is(type == IHipTexture)) 110 return HipAssetManager.loadTexture(assetPath); 111 else static if(is(type == IHipTextureAtlas)) 112 return HipAssetManager.loadTextureAtlas(assetPath); 113 else static if(is(type == IHipTilemap)) 114 return HipAssetManager.loadTilemap(assetPath); 115 else static if(is(type == IHipTileset)) 116 return HipAssetManager.loadTileset(assetPath); 117 else static if(is(type == IHipAudioClip)) 118 return HipAssetManager.loadAudio(assetPath); 119 else 120 return HipAssetManager.loadFile(assetPath); 121 } 122 IHipAssetLoadTask[] loadAsset(type)(string assetPath, int start, int end) 123 { 124 int sign = end - start >= 0 ? 1 : -1; 125 ///Include 1 for the upper bounds 126 int count = ((end - start) * sign) + 1; 127 if(count == 1) return [loadAsset!type(assetPath)]; 128 IHipAssetLoadTask[] ret = new IHipAssetLoadTask[count]; 129 130 static string formatStr(string str, int number) 131 { 132 import hip.util.to_string_range; 133 char[32] numSink = 0xff; 134 toStringRange(numSink[], number); 135 int charCount = 0; 136 while(numSink[charCount++] != 0xff){} charCount--; 137 //-1 for the $ 138 char[] formattedStr = new char[(cast(int)str.length)-1+charCount]; 139 int i = 0; 140 foreach(ch; str) 141 { 142 if(ch == '$') 143 formattedStr[i..i+=charCount] = numSink[0..charCount]; 144 else 145 formattedStr[i++] = ch; 146 } 147 return formattedStr; 148 } 149 150 foreach(i; 0..count) 151 ret[i] = loadAsset!type(formatStr(assetPath, start+i*sign)); 152 return ret; 153 } 154 155 mixin template LoadAllAssets(string modules) 156 { 157 import hip.api.data.commons; 158 import std.file; 159 mixin LoadReferencedAssets!(splitLines(modules)); 160 } 161 mixin template LoadReferencedAssets(string[] modules) 162 { 163 void loadReferenced() 164 { 165 static foreach(modStr; modules) 166 {{ 167 mixin("import ",modStr,";"); 168 alias theModule = mixin(modStr); 169 static foreach(moduleMemberStr; __traits(allMembers, theModule)) 170 {{ 171 alias moduleMember = __traits(getMember, theModule, moduleMemberStr); 172 static if(!is(moduleMember == module) && is(moduleMember type)) 173 { 174 static if(is(type == class) || is(type == struct)) 175 { 176 static foreach(classMemberStr; __traits(derivedMembers, type)) 177 {{ 178 alias classMember = __traits(getMember, type, classMemberStr); 179 alias assetUDA = GetAssetUDA!(__traits(getAttributes, classMember)); 180 // pragma(msg, assetUDA); 181 static if(assetUDA.path !is null) 182 {{ 183 import std.traits:isArray; 184 static if(isArray!(typeof(classMember))) alias memberType = typeof(classMember.init[0]); 185 else alias memberType = typeof(classMember); 186 187 IHipAssetLoadTask[] tasks = hip.api.data.commons.loadAsset!(memberType)(assetUDA.path, assetUDA.start, assetUDA.end); 188 static if(!__traits(compiles, classMember.offsetof)) //Static 189 { 190 191 void loadTaskInto(IHipAssetLoadTask task, ref memberType member) 192 { 193 static if(__traits(hasMember, assetUDA, "conversionFunction")) 194 task.into(assetUDA.conversionFunction, &member); 195 else static if(is(memberType == string)) 196 task.into(&member); 197 else 198 task.into!(memberType)(&member); 199 } 200 static if(isArray!(typeof(classMember))) 201 { 202 size_t start = classMember.length; 203 classMember.length+= tasks.length; 204 foreach(i, task; tasks) 205 loadTaskInto(task, classMember[start+i]); 206 } 207 else 208 loadTaskInto(tasks[0], classMember); 209 } 210 }} 211 }} 212 } 213 } 214 }} 215 }} 216 } 217 } 218 219 220 ///foreachAsset: void foreachAsset(T)(string assetPath) 221 mixin template ForeachAssetInClass(T, alias foreachAsset) 222 { 223 void ForeachAssetInClass() 224 { 225 import std.traits:isFunction; 226 static foreach(member; __traits(derivedMembers, T)) 227 {{ 228 alias theMember = __traits(getMember, T, member); 229 static if(!isFunction!theMember) 230 { 231 alias type = typeof(theMember); 232 enum assetUDA = GetAssetUDA!(__traits(getAttributes, theMember)); 233 static if(assetUDA.path != null) 234 foreachAsset!(type, theMember)(assetUDA.path); 235 } 236 }} 237 } 238 } 239 240 mixin template PreloadAssets() 241 { 242 private void _load(type, alias theMember)(string assetPath) 243 { 244 loadAsset!type(assetPath).into(&theMember); 245 } 246 alias preload = ForeachAssetInClass!(typeof(this), _load); 247 } 248 249 /** 250 * Usage: 251 ```d 252 class SomeScene : IHipPreloadable 253 { 254 mixin Preload; ///IHipPreloadable lets you use Preload symbol. 255 256 ///Will load "someTexture.png" inside the member 'texture' 257 @Asset("someTexture.png") 258 IHipTexture texture; 259 260 ///Loads game levels inside this variable 261 @Asset("gameLevels.txt", &parseGameLevels) 262 GameLevel[] gameLevels 263 264 ///Doesn't need to call 'preload()' to populate. As it is variable, it will be populated right after its load. 265 @Asset("helpText.txt") 266 static string helpText; 267 268 void initialize() 269 { 270 preload(); ///Needed to call for populating your assets after the class creation 271 } 272 273 GameLevel[] parseGameLevels(string data){return [];} 274 } 275 ``` 276 */ 277 interface IHipPreloadable 278 { 279 void preload(); 280 string[] getAssetsForPreload(); 281 282 mixin template Preload() 283 { 284 mixin template finalImpl() 285 { 286 private __gshared string[] _assetsForPreload; 287 private __gshared void getAsset(T, alias member)(string asset){_assetsForPreload~= asset;} 288 private final void loadAsset(T, alias member)(string asset) 289 { 290 alias mem = member; 291 ///Take members that aren't static and populate them after loading. 292 static if(__traits(compiles, mem.offsetof)) 293 { 294 ///Try converting the member with conversion function 295 static if(!__traits(compiles, HipAssetManager.get!T)) 296 { 297 alias assetUDA = GetAssetUDA!(__traits(getAttributes, mem)); 298 static assert(__traits(hasMember, assetUDA, "conversionFunction"), 299 "Type has no conversion function and HipAssetManager can't infer its type."); 300 mem = assetUDA.conversionFunction(HipAssetManager.get!string(asset)); 301 } 302 else //Just get from asset manager 303 mem = HipAssetManager.get!T(asset); 304 } 305 } 306 } 307 mixin template impl() 308 { 309 string[] getAssetsForPreload() 310 { 311 if(_assetsForPreload.length == 0) 312 { 313 mixin ForeachAssetInClass!(typeof(this), __traits(child, this, getAsset)) f; 314 f.ForeachAssetInClass; 315 } 316 return _assetsForPreload; 317 } 318 void preload() 319 { 320 mixin ForeachAssetInClass!(typeof(this), loadAsset) f; 321 f.ForeachAssetInClass; 322 } 323 } 324 325 326 ///Deal with override/no override 327 mixin finalImpl; 328 static if(__traits(compiles, typeof(super).preload)){override: mixin impl;} 329 else{mixin impl;} 330 } 331 } 332 333 334 interface ILoadable 335 { 336 /** Should return if the asset is ready for use*/ 337 bool isReady(); 338 } 339 340 /** 341 * OpenGL Renderer must implement IReloadable for when changing device orientation. 342 */ 343 interface IReloadable 344 { 345 bool reload(); 346 } 347 348 interface IHipAsset 349 { 350 string name() const; 351 string name(string newName); 352 353 uint assetID() const; 354 uint typeID() const; 355 } 356 357 358 enum HipAssetResult 359 { 360 cantLoad, 361 loading, 362 loaded 363 } 364 365 /** 366 * IHipAssetLoadTask is the base return type from any asset you want to `HipAssetManager.load{X}`. 367 * The loading, unless otherwise stated, is asynchronous. For simple games, most of the time you won't need 368 * to directly used LoadTask as currently the engine loads all the assets at startup to make it easier 369 * to prototype a game without needing to think about those tasks. 370 * 371 * `await` is not supported on WebAssembly export, so, don't use it if you plan to export to web. 372 */ 373 interface IHipAssetLoadTask 374 { 375 HipAssetResult result() const; 376 ///Sets the result. Should not exist in user code. 377 HipAssetResult result(HipAssetResult result); 378 379 IHipAsset asset(); 380 ///Sets the asset. Should not exist in user code. 381 IHipAsset asset(IHipAsset asset); 382 383 bool hasFinishedLoading() const; 384 ///Awaits the asset load process. Can't be used on WebAssembly export 385 void await(); 386 ///When the variables finish loading, it will also assign the asset to the variables 387 void into(void* function(IHipAsset asset) castFunc, IHipAsset*[] variables...); 388 final void into(T)(T*[] variables...){into((IHipAsset asset) => (cast(void*)cast(T)asset), cast(IHipAsset*[])variables);} 389 void into(string*[] variables...); 390 391 ///May be executed instantly if the asset is already loaded. 392 void addOnCompleteHandler(void delegate(IHipAsset) onComplete); 393 void addOnCompleteHandler(void delegate(string) onComplete); 394 final void into(T)(T function(string) convertFunction, T*[] variables...) 395 { 396 T*[] vars = variables.dup; 397 addOnCompleteHandler((string data) 398 { 399 foreach(v; vars) 400 *v = convertFunction(data); 401 }); 402 } 403 404 405 /** 406 * Awaits the asset to be loaded and if the load was possible, cast it to the type, else returns null. 407 * Unsupported at WebAssembly. 408 */ 409 T awaitAs(T)() 410 { 411 await(); 412 //Ignore dynamic cast (future only) return cast(T)(cast(void*)asset); 413 if(hasFinishedLoading() && result == HipAssetResult.loaded) 414 return cast(T)asset; 415 return null; 416 } 417 } 418 419 420 ///Maybe will be deprecated in future. This is common in web, but it is a pain to work with. 421 interface IHipDeferrableTexture 422 { 423 void setTexture(IHipAssetLoadTask task); 424 } 425 interface IHipDeferrableText 426 { 427 void setFont(IHipAssetLoadTask task); 428 } 429 430 interface IHipDeserializable 431 { 432 IHipDeserializable deserialize(string data); 433 IHipDeserializable deserialize(void* data); 434 }